<?php

error_reporting(E_ALL);

/*
 *
 * Backup Monitor Bot By Phil
 * Copyright Phil 2010
 *
 */

// Config
include('config.inc.php');

// Variable setting
$twitters = array();
$timers = array();
$timers['ping'] = 0;
$timers['poll'] = 0;
$read = null;

// Functions
function send($sock, $message, $output = true) {
	fputs($sock, "{$message}\n");

	if ($output) {
		echo "(" . date('H:i') . ") <- {$message}\n";
	}
}

function shouldPart($chan) {
	global $twitters;

	foreach ($twitters as $twitter) {
		$twitter->resetChannelIterator();

		while ($twitter->hasNextChannel()) {
			if ($twitter->nextChannel() == $chan) {
				return false;
			}
		}
	}

	return true;
}

function exceptionHandler($e) {
	echo "EXCEPTION CAUGHT " . get_class($e) . ": {$e->getMessage()}\n";

	$handle = fopen(LOG_FILE, 'a');
	fwrite($handle, "EXCEPTION CAUGHT " . get_class($e) . ": {$e->getMessage()}\n");
	fclose($handle);
}

function my_mysql_query($query, $link_identifier = false) {
	if (!mysql_ping()) {
		echo 'MySQL is buggered! Reconnecting' . "\n";

		mysql_close();
		mysql_connect(THALES_HOST, THALES_USER, THALES_PASS);
		mysql_select_db(THALES_DB);
	}

	if ($link_identifier === false)
		return mysql_query($query);
	else
		return mysql_query($query, $link_identifier);
}

set_time_limit(0);

// Includes
include('twitter_lib.php');
include('Twitter.class.php');

// Formatting
define('BOLD', chr(2));
define('COLOR', chr(3));

// Load Objects
foreach (glob(SAVE_DIR . '/*.save') as $filename) {
	$contents = file_get_contents($filename);
	$twit = unserialize($contents);
	$twitters[$twit->name] = $twit;
}

// Connect to DB
mysql_connect(THALES_HOST, THALES_USER, THALES_PASS);
mysql_select_db(THALES_DB);

// The juice
while (1) {
	// Connect
	$sock = fsockopen(SERVER, PORT);

	// Bitch, Rinse and Repeat if it's broken
	if (!$sock) {
		echo 'Error connecting to ' . SERVER . ' on port ' . PORT . '! Trying again in 30 seconds';
		sleep(30);
	} else {
		// Stop the stream blocking
		stream_set_timeout($sock, 0, 1);

		// Send connect stuff
		send($sock, 'USER ' . IDENT . ' 0 * :' . GECOS);
		send($sock, 'NICK ' . BOT_NICK);
		send($sock, 'NS IDENTIFY ' . NSPASS);
		send($sock, 'UMODE2 -x');

		// Loop until socket dies
		while (!feof($sock)) {
			usleep(10000);

			// Increment all timers
			foreach ($timers as $key => $timer) {
				$timers[$key]++;
			}

			// Timer events
			if ($timers['ping'] == 6000) {
				send($sock, 'PING :TIMEOUTCHECK', false);
				$timers['ping'] = 0;
			}
			if ($timers['poll'] == POLL_TIME) {
				foreach ($twitters as $twitter) {
					$arr = null;

					try {
						if ($twitter->oAuthToken != null && $twitter->oAuthTokenSecret != null)
							$arr = $twitter->poll();
					}
					catch (Exception $e) {
						if ($e->getMessage() == 'This method requires authentication.') {
							$twitter->resetChannelIterator();

							while ($twitter->hasNextChannel()) {
								$chan = $twitter->nextChannel();

								$twitter->delChan($chan);

								if (shouldPart($chan)) {
									send($sock, "PART {$chan}");
								}
							}

							unset($twitters[$twitter->name]);

							rename(SAVE_DIR . "/{$twitter->name}.save", DELETED_DIR . "/{$twitter->name}.save");
						}
						else {
							exceptionHandler($e);
						}
					}

					if (is_array($arr) && count($arr) > 0) {
						foreach ($arr as $a) {
							$message = html_entity_decode($a);

							send($sock, "PRIVMSG {$twitter->getChansCSV()} :{$twitter->user_name}: {$message}");
						}

						$twitter->save(SAVE_DIR . "/{$twitter->name}.save");
					}
				}

				$timers['poll'] = 0;
			}

			// Reading data
			$read .= fgets($sock, 512);

			// Loop the individual lines
			while (substr_count($read, "\n") != 0) {
				// Get end of line
				$offset = strpos($read, "\n");
				// Get data
				$data = substr($read, 0, $offset);
				// Remove data from end of line
				$read = substr($read, $offset + 1);

				// Print the data for all but PONG
				if (strpos($data, 'PONG') === false) {
					echo "(" . date('H:i') . ") -> {$data}\n";
				}

				// Split the data
				$dataSplit = explode(' ', $data);

				// Handle the data
				// PINGs
				if ($dataSplit[0] == 'PING') {
					send($sock, 'PONG ' . substr($data, 5));
				}
				// Numeric messages
				elseif (is_numeric($dataSplit[1])) {
					if ($dataSplit[1] == 433) {
						// Send alt nick
						send($sock, "NICK " . BOT_ANICK);
						// Resend identify and JOIN commands
						send($sock, 'NS IDENTIFY ' . NSPASS);
					}
				} elseif ($dataSplit[1] == 'PRIVMSG') {
					$message = trim(ltrim(implode(' ', array_slice($dataSplit, 3)), ':'));
					$words = explode(' ', trim($message));
					$to = $dataSplit[2];
					$nick = explode('!', $dataSplit[0]);
					$nick = ltrim($nick[0], ':');

					if (strtolower($to) == strtolower(BOT_NICK)) {
						switch ($words[0]) {
							case '!register':
								if (count($words) < 3 || trim($words[1]) == '' || trim($words[2]) == '') {
									send($sock, "PRIVMSG {$nick} :Syntax: !register <bot_username> <bot_password>");
								} else {
									$stop = false;

									foreach ($twitters as $twitter) {
										if ($twitter->name == $words[1]) {
											send($sock, "PRIVMSG {$nick} :That name is in use");
											$stop = true;
										}
									}

									if (!$stop) {
										try {
											// create instance
											$twitterLib = new TwitterLib(CONSUMER_KEY, CONSUMER_SECRET);

											// get a request token
											$response = $twitterLib->oAuthRequestToken('oob');
										}
										catch (Exception $e) {
											exceptionHandler($e);
										}

										if (isset($response)) {
											$twit = new Twitter($words[1], $words[2], $response['oauth_token']);
											$twitters[$words[1]] = $twit;

											// Notify the user
											$url = "https://api.twitter.com/oauth/authorize?oauth_token={$response['oauth_token']}";
											send($sock, "PRIVMSG {$nick} :Please visit {$url} and obtain your PIN number. Then, return to this PM and type \"!pin <bot_username> <bot_password> <yourpin>\" for example: !pin fred mysecretpass 6789567");

											// Save the object
											$twit->save(SAVE_DIR . "/{$twit->name}.save");

											unset($response);
										}
										else {
											send($sock, "PRIVMSG {$nick} :Twitter is broken. Please try again later.");
										}
									}
								}

								break;
							case '!pin':
								if (count($words) < 4) {
									send($sock, "PRIVMSG {$nick} :Syntax: !pin <bot_username> <bot_password> <pin>");
								} else {
									$twit = null;

									foreach ($twitters as $twitter) {
										if ($twitter->name == $words[1]) {
											if ($twitter->checkPass($words[2])) {
												$twit = $twitter;
											}
										}
									}

									if ($twit === null) {
										send($sock, "PRIVMSG {$nick} :Name/Password Error!");
									} else {
										$oauth_token = $twit->reqToken;
										$oauth_verifier = $words[3];

										// get tokens
										try {
											$twitterLib = new TwitterLib(CONSUMER_KEY, CONSUMER_SECRET);
											$response = $twitterLib->oAuthAccessToken($oauth_token, $oauth_verifier);
										}
										catch (Exception $e) {
											exceptionHandler($e);
										}

										if (isset($response) && isset($response['oauth_token'])) {
											// Save data returned
											$twit->setTokens($response['oauth_token'], $response['oauth_token_secret']);
											$twit->setDetails($response['user_id'], $response['screen_name']);

											// Poll and eat it so we don't flood
											try {
												$twit->poll();
											}
											catch (Exception $e) {
												exceptionHandler($e);
											}

											// Notify the user
											send($sock, "PRIVMSG {$nick} :Registration complete!");

											// Save the object
											$twit->save(SAVE_DIR . "/{$twit->name}.save");

											unset($response);
										} else {
											send($sock, "PRIVMSG {$nick} :Pin incorrect. Please try again!");
										}
									}
								}
								break;
							case '!addchan':
								if (count($words) < 4 || trim($words[3]) == '') {
									send($sock, "PRIVMSG {$nick} :Syntax: !addchan <bot_username> <bot_password> <channel>");
								} else {
									$twit = null;

									foreach ($twitters as $twitter) {
										if ($twitter->name == $words[1]) {
											if ($twitter->checkPass($words[2])) {
												$twit = $twitter;
											}
										}
									}

									if ($twit === null) {
										send($sock, "PRIVMSG {$nick} :Name/Password Error!");
									} else if ($twit->numChans() > 10) {
										send($sock, "PRIVMSG {$nick} :You can have a maximum of 10 channels");
									} else if ($twit->isChan($words[3])) {
										send($sock, "PRIVMSG {$nick} :Duplicate channel {$words[3]}");
									} else {
										// Check user is op on chan
										$nSql = mysql_real_escape_string($nick);
										$cSql = mysql_real_escape_string(strtolower($words[3]));

										$sql = "SELECT COUNT(*) AS `cnt` FROM chan c INNER JOIN ison i ON c.chanid=i.chanid INNER JOIN user u ON u.nickid=i.nickid WHERE (u.nick='{$nSql}' AND c.channel='{$cSql}' AND ((i.mode_la = 'Y' OR i.mode_lo = 'Y' OR i.mode_lq = 'Y') OR (u.mode_lo = 'Y')))";

										$result = my_mysql_query($sql);
										$line = mysql_fetch_assoc($result);

										if ($line['cnt'] > 0) {
											$twit->addChan($words[3]);
											$twit->save(SAVE_DIR . "/{$twit->name}.save");
											send($sock, "JOIN {$words[3]}");
											send($sock, "PRIVMSG {$nick} :Channel {$words[3]} added");
										} else {
											send($sock, "PRIVMSG {$nick} :You are not an op in {$words[3]}");
										}
									}
								}

								break;
							case '!delchan':
								if (count($words) < 4) {
									send($sock, "PRIVMSG {$nick} :Syntax: !delchan <bot_username> <bot_password> <channel>");
								} else {
									$twit = null;

									foreach ($twitters as $twitter) {
										if ($twitter->name == $words[1]) {
											if ($twitter->checkPass($words[2])) {
												$twit = $twitter;
											}
										}
									}

									if ($twit === null) {
										send($sock, "PRIVMSG {$nick} :Name/Password Error!");
									} else if (!$twit->isChan($words[3])) {
										send($sock, "PRIVMSG {$nick} :Channel {$words[3]} not added");
									} else {
										$twit->delChan($words[3]);
										$twit->save(SAVE_DIR . "/{$twit->name}.save");
										if (shouldPart($words[3])) {
											send($sock, "PART {$words[3]}");
										}
										send($sock, "PRIVMSG {$nick} :Channel {$words[3]} deleted");
									}
								}

								break;
							case '!drop':
								if (count($words) < 3) {
									send($sock, "PRIVMSG {$nick} :Syntax: !drop <bot_username> <bot_password>");
								} else {
									$twit = null;

									foreach ($twitters as $twitter) {
										if ($twitter->name == $words[1]) {
											if ($twitter->checkPass($words[2])) {
												$twit = $twitter;
											}
										}
									}

									if ($twit === null) {
										send($sock, "PRIVMSG {$nick} :Name/Password Error!");
									} else {
										$twit->resetChannelIterator();

										while ($twit->hasNextChannel()) {
											$chan = $twit->nextChannel();

											$twit->delChan($chan);

											if (shouldPart($chan)) {
												send($sock, "PART {$chan}");
											}
										}

										unset($twitters[$words[1]]);
										unlink(SAVE_DIR . "/{$twit->name}.save");

										send($sock, "PRIVMSG {$nick} :Registration {$words[1]} dropped");
									}
								}

								break;
							case '!help':
								send($sock, "PRIVMSG {$nick} :Available Commands:");
								send($sock, "PRIVMSG {$nick} :    !help       Show this help");
								send($sock, "PRIVMSG {$nick} :    !register   Register your twitter account");
								send($sock, "PRIVMSG {$nick} :    !pin        Tell the bot your twitter PIN number");
								send($sock, "PRIVMSG {$nick} :    !drop       Drops the registration of your account");
								send($sock, "PRIVMSG {$nick} :    !addchan    Add channel to notification list for your twitter feed");
								send($sock, "PRIVMSG {$nick} :    !delchan    Remove channel from notification list for your twitter feed");
								send($sock, "PRIVMSG {$nick} : ");
								send($sock, "PRIVMSG {$nick} :Type the command with no params to get help with it.");

								break;
						}
					}
				} elseif ($dataSplit[1] == 'NOTICE') {
					$message = trim(ltrim(implode(' ', array_slice($dataSplit, 3)), ':'));
					$words = explode(' ', trim($message));
					$to = $dataSplit[2];
					$nick = explode('!', $dataSplit[0]);
					$nick = ltrim($nick[0], ':');

					if ($nick == 'NickServ' && $to == BOT_NICK) {
						if ($words[0] . ' ' . $words[1] == 'Password accepted') {
							// Join chans
							foreach ($twitters as $twitter) {
								$twitter->resetChannelIterator();

								while ($twitter->hasNextChannel()) {
									$chan = $twitter->nextChannel();
									send($sock, 'JOIN ' . $chan);
								}
							}
						}
					}
				}
			}
		}
	}
}
?>
